﻿//Copyright (C) <2013>  <jonathan cleeve norton> All Rights Reserved 
//Contact jon.norton@fin-plus.co.uk website <http://www.fin-plus.co.uk/>
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Linq;
using System.ComponentModel;
using FinPlusCommon;
using log = Logger.Logger;
using u = FinPlusUtility.Utility;

namespace FinPlusComponents
{
    public class QLEngineDefaultCurves : FinPlusComponent
    {
        private enum Params { CurveName, LinkedCurve };
        private enum BuildEvents { Update, Refresh, UpdateAndRefresh }
        private readonly IFinPlusComp _instrumentConn, _defaultCurveBuildConn, _instrumentBuildConn;
        private readonly QLAnalytics _analytics;
        private readonly DefaultCurves _defaultCurves;
        private readonly BuildEvents _buildEvents;

        //construct
        public QLEngineDefaultCurves(IFinPlusComp analytics, IFinPlusComp defaultCurveBuilds, IFinPlusComp _instrumentBuilds, IFinPlusComp instruments, string buildEvents)
        {
            _analytics = (QLAnalytics)analytics;
            _defaultCurveBuildConn =defaultCurveBuilds;
            _instrumentBuildConn = _instrumentBuilds;
            _instrumentConn = instruments;
            _buildEvents = u.EnumParse<BuildEvents>(buildEvents, BuildEvents.UpdateAndRefresh);
            _defaultCurves = new DefaultCurves();

            foreach (var defaultCurveBuild in _defaultCurveBuildConn.Adaptor.Values)
            {
                var curveName = defaultCurveBuild[Params.CurveName.ToString()].ToString();
                var insts = _instrumentConn.Adaptor.ToList(Params.LinkedCurve.ToString(), curveName, ',');
                _defaultCurves.TryAdd(curveName, new DefaultCurve(_analytics, curveName, defaultCurveBuild, _instrumentBuildConn.Adaptor, insts, _buildEvents));
            }

            IsValid = true;
        }

        //common control interface
        public override void Dispose() 
        {
            try
            {
                if (_instrumentConn != null)
                    _instrumentConn.Dispose();
                if (_defaultCurveBuildConn != null)
                    _defaultCurveBuildConn.Dispose();
                if (_analytics != null)
                    _analytics.Dispose();
            }
            catch (Exception e)
            {
                log.Log(Logger.Level.Warning, e.Message, e);
            }
        
        }

        //all helper class of component FinPlusEngineDefaultCurves 
        private class DefaultCurves : ConcurrentDictionary<string, DefaultCurve> { }

        private class DefaultCurve : ConcurrentDictionary<string, CdsSpread>
        {
            private enum Params { LevelUpdate, Refresh, Shock, Instrument, CurveName, NumSpread, RecoveryRate, DayCount, Holidays };

            private IList<IFunction> _instruments;
            private QLAnalytics _analytics;
            private IFunctions _instrumentBuilds;
            private IFunction _defaultCurveBuild;
            private BuildEvents _buildEvents;
            private string _curveName, _dayCount, _holidays;
            private int _numSpread, _spreadCount;
            private double _recoveryRate;
  
            //construct
            public DefaultCurve(QLAnalytics analytics, string curveName, IFunction defaultCurveBuild, IFunctions instrumentBuilds, IList<IFunction> instruments, BuildEvents buildEvents)
            {
                _analytics = analytics;
                _curveName = curveName;
                _defaultCurveBuild = defaultCurveBuild;
                _instrumentBuilds = instrumentBuilds;
                _instruments = instruments;
                _recoveryRate = (double)_defaultCurveBuild.Value(Params.RecoveryRate.ToString());
                _dayCount = _defaultCurveBuild[Params.DayCount.ToString()].ToString();
                _holidays = _defaultCurveBuild[Params.Holidays.ToString()].ToString();
                _numSpread = (int)_defaultCurveBuild.Value(Params.NumSpread.ToString());
                _buildEvents = buildEvents;
                Initialise();
            }

            //public
            public void Initialise()
            {
                foreach (var instrument in _instruments)
                {
                    if (_buildEvents == BuildEvents.Refresh || _buildEvents == BuildEvents.UpdateAndRefresh)
                        instrument.Get(Params.Refresh.ToString(), string.Empty, Curve_Refresh);

                    instrument.Get(Params.LevelUpdate.ToString(), new Function(), Curve_Update);
                    instrument.Get(Params.Shock.ToString(), new Function(), Curve_Shock);

                    var instrumentName = instrument[Params.Instrument.ToString()].ToString();
                    var instrumentBuild = _instrumentBuilds.First("Name='Instrument' And Value='" + instrumentName + "'");

                    if (instrumentBuild != null)
                    {
                        this[instrumentName] = AddSpread(instrumentName, instrument, instrumentBuild);
                        instrument.Get(Params.Refresh.ToString(), string.Empty).Set(instrumentName);
                    }
                }
            }

            //private
            private CdsSpread AddSpread(string instrumentName, IFunction instrument, IFunction instrumentBuild)
            {
                _spreadCount++;
                return new CdsSpread(_analytics, instrumentName, instrument, instrumentBuild, _recoveryRate);
            }

            private void BuildCurve()
            {
                try
                {
                    string res;
                    lock (_analytics.QL)
                        res = _analytics.QL.DefaultCurve(_analytics.MarketName, _curveName, _analytics.DiscountCurve, _analytics.AsOf, Spreads(_analytics.BuySell), _recoveryRate, _dayCount, _holidays);
                }
                catch (Exception e)
                {
                    log.Log(Logger.Level.Error, e.Message, e);
                }
            
            }

            private bool IsValid()
            {
                if (_numSpread != _spreadCount)
                    return false;

                foreach (var spread in this.Values)
                    if (Double.IsNaN(spread.Value))
                        return false;

                return true;
            }

            private string Spreads(string buySell)
            {
                return string.Join(",", this.Values.Select(f => f.Build(buySell)).ToArray<string>());
            }
            
            //events
            private void Curve_Update(object s, PropertyChangedEventArgs a)
            {
                var level = (IFunction)((IItem)s).Object;
                var instrument = level[Params.Instrument.ToString()].ToString();

                if (this[instrument].Update(level) && IsValid()) 
                    BuildCurve();
            }

            private void Curve_Refresh(object s, PropertyChangedEventArgs a)
            {
                if (IsValid() && (((IItem)s).Value.Equals(_curveName)))
                    BuildCurve();
            }

            private void Curve_Shock(object s, PropertyChangedEventArgs a)
            {
                var shock = (IFunction)((IItem)s).Object;
                var curveName = shock[Params.CurveName.ToString()].ToString();
                if (curveName.Equals(_curveName))
                {
                    var instrumentName = shock[Params.Instrument.ToString()].ToString();
                    var value = (int)shock.Get(Params.Shock.ToString()).Object;
                    this[instrumentName].Shock = value;
                    if (value != 0 && IsValid()) BuildCurve();
                }
            }
        }

        private class CdsSpread 
        {
            public double Value { get { return _level.Value; } }
            public int Shock { get; set; }
            public string Name { get { return _instrumentName; } }

            private enum Params { ShockUnit, Maturity, SettlementDays, DayCount, PremFreq, PremConv, Holidays };
            private QLAnalytics _analytics;
            private FinPlusLevel _level;
            private IFunction _instrumentBuild, _instrument;
            private string _instrumentName, _maturity, _dayCount, _premConv, _holidays, _premFreq;
            private int _settlementDays;
            private double _recoveryRate, _shockUnit;

            public CdsSpread(QLAnalytics analytics, string instrumentName, IFunction instrument, IFunction instrumentBuild, double recoveryRate)
            {
                _analytics = analytics;
                _instrumentName = instrumentName;
                _instrument = instrument;
                _instrumentBuild = instrumentBuild;
                _recoveryRate = recoveryRate;

                _settlementDays = (int)_instrumentBuild.Value(Params.SettlementDays.ToString());
                _level = new FinPlusLevel(analytics.BuySell);
                _maturity = _instrumentBuild[Params.Maturity.ToString()].ToString();
                _settlementDays = (int)_instrumentBuild.Value(Params.SettlementDays.ToString());
                _dayCount = _instrumentBuild[Params.DayCount.ToString()].ToString();
                _premFreq = _instrumentBuild[Params.PremFreq.ToString()].ToString();
                _premConv = _instrumentBuild[Params.PremConv.ToString()].ToString();
                _holidays = _instrumentBuild[Params.Holidays.ToString()].ToString();
                _shockUnit = (double)_instrument.Get(Params.ShockUnit.ToString()).Object;
            }

            public string Build(string buySell)
            {
                lock (_analytics.QL)
                    return _analytics.QL.CdsSpread(_analytics.MarketName, _instrumentName, _level.Value + _shockUnit * Shock, _recoveryRate, _maturity, _settlementDays, _analytics.DiscountCurve, _premFreq, _dayCount, _premConv, _holidays);
            }

            internal bool Update(IFunction level)
            {
                lock (_analytics.QL)
                   return _level.LevelUpdate(level);
            }
        }
    }
}
